// 导入文件系统模块 'fs' 并使用 'mz' 提供的 Promises 接口
import * as fs from 'mz/fs'
// 导入路径处理模块 'path'
import * as path from 'path'
// 导入 Electron 的远程模块
import * as remote from '@electron/remote'
// 导入 PluginInfo 类型定义
import { PluginInfo } from '../../tabby-core/src/api/mainProcess'
// 导入插件黑名单
import { PLUGIN_BLACKLIST } from './pluginBlacklist'

// 使用 require 语法导入 Node.js 的 module 模块
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires

// 保存全局的 require 函数
const nodeRequire = global['require']

/**
 * 规范化路径，将 Cygwin 风格的路径转换为 Windows 风格的路径
 * @param p 输入的路径字符串
 * @returns 规范化后的路径字符串
 */
function normalizePath (p: string): string {
    const cygwinPrefix = '/cygdrive/'
    // 如果路径以 Cygwin 前缀开头
    if (p.startsWith(cygwinPrefix)) {
        // 去掉前缀并替换 '/' 为 '\'
        p = p.substring(cygwinPrefix.length).replace('/', '\\')
        // 将路径的开头部分转换为 Windows 风格的盘符
        p = p[0] + ':' + p.substring(1)
    }
    return p
}

// 定义内置插件路径，根据是否为开发环境选择不同的路径
const builtinPluginsPath = process.env.TABBY_DEV ? path.dirname(remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')

// 缓存的内置模块，避免重复加载
const cachedBuiltinModules = {
    '@angular/animations': require('@angular/animations'),
    '@angular/cdk/drag-drop': require('@angular/cdk/drag-drop'),
    '@angular/cdk/clipboard': require('@angular/cdk/clipboard'),
    '@angular/common': require('@angular/common'),
    '@angular/compiler': require('@angular/compiler'),
    '@angular/core': require('@angular/core'),
    '@angular/forms': require('@angular/forms'),
    '@angular/localize': require('@angular/localize'),
    '@angular/localize/init': require('@angular/localize/init'),
    '@angular/platform-browser': require('@angular/platform-browser'),
    '@angular/platform-browser/animations': require('@angular/platform-browser/animations'),
    '@angular/platform-browser-dynamic': require('@angular/platform-browser-dynamic'),
    '@ng-bootstrap/ng-bootstrap': require('@ng-bootstrap/ng-bootstrap'),
    'ngx-toastr': require('ngx-toastr'),
    rxjs: require('rxjs'),
    'rxjs/operators': require('rxjs/operators'),
    'zone.js/dist/zone.js': require('zone.js/dist/zone.js'),
}

// 内置模块的名称列表
const builtinModules = [
    ...Object.keys(cachedBuiltinModules),
    'tabby-core',
    'tabby-local',
    'tabby-settings',
    'tabby-terminal',
]

// 存储原始的全局 require 函数
const originalRequire = (global as any).require

// 重写全局 require 函数，优先从缓存中加载内置模块
;(global as any).require = function (query: string) {
    if (cachedBuiltinModules[query]) {
        return cachedBuiltinModules[query]
    }
    return originalRequire.apply(this, [query])
}

// 存储原始的模块 require 函数
const originalModuleRequire = nodeModule.prototype.require

// 重写模块的 require 函数，优先从缓存中加载内置模块
nodeModule.prototype.require = function (query: string) {
    if (cachedBuiltinModules[query]) {
        return cachedBuiltinModules[query]
    }
    return originalModuleRequire.call(this, query)
}
// 定义一个类型别名 ProgressCallback，表示一个接受当前进度和总进度的回调函数类型
export type ProgressCallback = (current: number, total: number) => void

/**
 * 初始化模块查找路径
 * @param userPluginsPath 用户插件路径
 */
export function initModuleLookup(userPluginsPath: string): void {
    // 将全局模块路径规范化并加入全局模块路径数组中
    global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
    console.log('------------------>Global paths:googo ', nodeModule.globalPaths)
    const paths = []
    // 插入用户插件的 node_modules 路径
    paths.unshift(path.join(userPluginsPath, 'node_modules'))
    // 插入应用程序的 node_modules 路径
    paths.unshift(path.join(remote.app.getAppPath(), 'node_modules'))

    // 如果是开发环境，插入应用程序路径
    if (process.env.TABBY_DEV) {
        paths.unshift(path.dirname(remote.app.getAppPath()))
    }

    // 插入内置插件路径
    paths.unshift(builtinPluginsPath)
    // paths.unshift(path.join((process as any).resourcesPath, 'app.asar', 'node_modules'))

    // 如果环境变量 TABBY_PLUGINS 存在，拆分并插入路径
    if (process.env.TABBY_PLUGINS) {
        process.env.TABBY_PLUGINS.split(':').map(x => paths.push(normalizePath(x)))
    }

    // 将路径数组转换为字符串并加入环境变量 NODE_PATH
    process.env.NODE_PATH += path.delimiter + paths.join(path.delimiter)
    // 初始化模块查找路径
    nodeModule._initPaths()

    // 缓存内置模块，避免重复加载
    builtinModules.forEach(m => {
        if (!cachedBuiltinModules[m]) {
            cachedBuiltinModules[m] = nodeRequire(m)
        }
    })
}

// 插件前缀常量
const PLUGIN_PREFIX = 'tabby-'
// 旧版插件前缀常量
const LEGACY_PLUGIN_PREFIX = 'terminus-'

/**
 * 获取插件目录中的候选位置
 * @param pluginDir 插件目录
 * @returns 返回包含插件目录和包名的候选位置数组
 */
async function getCandidateLocationsInPluginDir(pluginDir: any): Promise<{ pluginDir: string, packageName: string }[]> {
    const candidateLocations: { pluginDir: string, packageName: string }[] = []

    // 检查插件目录是否存在
    if (await fs.exists(pluginDir)) {
        // 读取插件目录下的所有文件夹名称
        const pluginNames = await fs.readdir(pluginDir)

        // 如果插件目录中存在 package.json 文件，将其加入候选位置
        if (await fs.exists(path.join(pluginDir, 'package.json'))) {
            candidateLocations.push({
                pluginDir: path.dirname(pluginDir),
                packageName: path.basename(pluginDir),
            })
        }

        const promises = []

        // 遍历所有插件名称
        for (const packageName of pluginNames) {
            // 过滤出符合前缀且不在黑名单中的插件
            if ((packageName.startsWith(PLUGIN_PREFIX) || packageName.startsWith(LEGACY_PLUGIN_PREFIX)) && !PLUGIN_BLACKLIST.includes(packageName)) {
                const pluginPath = path.join(pluginDir, packageName)
                const infoPath = path.join(pluginPath, 'package.json')
                // 检查插件路径下是否存在 package.json 文件，存在则加入候选位置
                promises.push(fs.exists(infoPath).then(result => {
                    if (result) {
                        candidateLocations.push({ pluginDir, packageName })
                    }
                }))
            }
        }

        // 等待所有检查操作完成
        await Promise.all(promises)
    }

    return candidateLocations
}
/**
 * 获取插件候选位置
 * @param paths 插件路径数组
 * @returns 返回包含插件目录和包名的候选位置数组
 */
async function getPluginCandidateLocation(paths: any): Promise<{ pluginDir: string, packageName: string }[]> {
    const candidateLocationsPromises: Promise<{ pluginDir: string, packageName: string }[]>[] = []
    const processedPaths = []

    // 遍历所有路径
    for (let pluginDir of paths) {
        // 如果路径已经处理过，则跳过
        if (processedPaths.includes(pluginDir)) {
            continue
        }
        processedPaths.push(pluginDir)

        // 规范化路径
        pluginDir = normalizePath(pluginDir)

        // 获取候选位置并加入 promises 数组
        candidateLocationsPromises.push(getCandidateLocationsInPluginDir(pluginDir))
    }

    // 等待所有候选位置获取操作完成
    const candidateLocations: { pluginDir: string, packageName: string }[] = []
    for (const pluginCandidateLocations of await Promise.all(candidateLocationsPromises)) {
        candidateLocations.push(...pluginCandidateLocations)
    }

    return candidateLocations
}

/**
 * 解析插件信息
 * @param pluginDir 插件目录
 * @param packageName 包名
 * @returns 返回插件信息对象或 null
 */
async function parsePluginInfo(pluginDir: string, packageName: string): Promise<PluginInfo | null> {
    const pluginPath = path.join(pluginDir, packageName)
    const infoPath = path.join(pluginPath, 'package.json')

    // 根据前缀获取插件名称
    const name = packageName.startsWith(PLUGIN_PREFIX) ? packageName.substring(PLUGIN_PREFIX.length) : packageName.substring(LEGACY_PLUGIN_PREFIX.length)

    try {
        // 读取并解析 package.json 文件
        const info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' }))

        // 检查关键词是否包含插件标识，如果不包含则返回 null
        if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin') || info.keywords.includes('tabby-plugin') || info.keywords.includes('tabby-builtin-plugin'))) {
            return null
        }

        // 获取作者信息
        let author = info.author
        author = author.name || author

        console.log(`Found ${name} in ${pluginDir}`)

        // 返回插件信息对象
        return {
            name: name,
            packageName: packageName,
            isBuiltin: pluginDir === builtinPluginsPath,
            isLegacy: info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'),
            version: info.version,
            description: info.description,
            author,
            path: pluginPath,
            info,
        }
    } catch (error) {
        // 捕获并处理读取或解析 package.json 的错误
        console.error('Cannot load package info for', packageName)
        return null
    }
}
/**
 * 查找插件
 * @returns 返回插件信息数组
 */
export async function findPlugins(): Promise<PluginInfo[]> {
    const paths = nodeModule.globalPaths
    let foundPlugins: PluginInfo[] = []

    // 获取插件候选位置
    const candidateLocations: { pluginDir: string, packageName: string }[] = await getPluginCandidateLocation(paths)

    const foundPluginsPromises: Promise<PluginInfo | null>[] = []
    for (const { pluginDir, packageName } of candidateLocations) {

        // 排除内置模块但路径不在内置插件路径中的情况
        if (builtinModules.includes(packageName) && pluginDir !== builtinPluginsPath) {
            continue
        }

        // 解析插件信息并加入 promises 数组
        foundPluginsPromises.push(parsePluginInfo(pluginDir, packageName))
    }

    // 等待所有插件信息解析完成
    for (const pluginInfo of await Promise.all(foundPluginsPromises)) {
        if (pluginInfo) {
            // 检查是否存在同名插件
            const existing = foundPlugins.find(x => x.name === pluginInfo.name)
            if (existing) {
                if (existing.isLegacy) {
                    // 如果是旧版本插件，覆盖现有插件
                    console.info(`Plugin ${pluginInfo.packageName} already exists, overriding`)
                    foundPlugins = foundPlugins.filter(x => x.name !== pluginInfo.name)
                } else {
                    // 否则跳过
                    console.info(`Plugin ${pluginInfo.packageName} already exists, skipping`)
                    continue
                }
            }

            // 添加插件到结果数组
            foundPlugins.push(pluginInfo)
        }
    }

    // 按照插件名称和是否内置进行排序
    foundPlugins.sort((a, b) => a.name > b.name ? 1 : -1)
    foundPlugins.sort((a, b) => a.isBuiltin < b.isBuiltin ? 1 : -1)
    return foundPlugins
}

/**
 * 加载插件
 * @param foundPlugins 查找到的插件信息数组
 * @param progress 进度回调函数
 * @returns 返回加载的插件数组
 */
export async function loadPlugins(foundPlugins: PluginInfo[], progress: ProgressCallback): Promise<any[]> {
    const plugins: any[] = []
    const pluginsPromises: Promise<any>[] = []

    let index = 0
    const setProgress = function () {
        index++
        progress(index, foundPlugins.length)
    }

    // 初始化进度
    progress(0, 1)
    for (const foundPlugin of foundPlugins) {
        pluginsPromises.push(new Promise(x => {
            console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`)
            try {
                // 加载插件模块
                const packageModule = nodeRequire(foundPlugin.path)
                if (foundPlugin.packageName.startsWith('tabby-')) {
                    // 缓存内置模块
                    cachedBuiltinModules[foundPlugin.packageName.replace('tabby-', 'terminus-')] = packageModule
                }
                // 初始化插件模块
                const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
                pluginModule.pluginName = foundPlugin.name
                pluginModule.bootstrap = packageModule.bootstrap
                plugins.push(pluginModule)
            } catch (error) {
                // 捕获并处理加载插件的错误
                console.error(`Could not load ${foundPlugin.name}:`, error)
            }
            // 更新进度
            setProgress()
            setTimeout(x, 50)
        }))
    }
    // 等待所有插件加载完成
    await Promise.all(pluginsPromises)

    // 完成进度
    progress(1, 1)
    return plugins
}
